/**
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


const success: number = 0;
const fail: number = 1;


function main(): int {
    let failures: number = 0;

    failures += check(createDefault(),"Create default empty {{.item.objectType}}");
    failures += check(createEmptyWithLength(),"Create default empty {{.item.objectType}} from length");
    failures += check(createEmptyWithNaNLength(),"Create default empty {{.item.objectType}} with NaN length");
    failures += check(createNonEmptyWithLength(),"Create default non empty {{.item.objectType}} from length");
    failures += check(createNonEmptyWithFloatingLength(),"Create default non empty {{.item.objectType}} with wrong floating length");
    failures += check(createtypedUArrayWithNegativeLength(),"Create default non empty {{.item.objectType}} with wrong negative length");
    failures += check(createtypedUArrayWithNegativeInfinityLength(),"Create default non empty {{.item.objectType}} with infinity negative length");
    failures += check(createtypedUArrayWithPositiveInfinityLength(),"Create default non empty {{.item.objectType}} with infinity positive length");
    failures += check(createtypedUArrayWithNegativeNaNLength(),"Create empty {{.item.objectType}} with negative NaN length");
    failures += check(createtypedUArrayWithPositiveNaNLength(),"Create empty {{.item.objectType}} with positive NaN length");
    failures += check(createEmptyWithIterable(),"Create default empty {{.item.objectType}} from Iterable");
    failures += check(createNonEmptyWithIterable(),"Create default non empty {{.item.objectType}} from Iterable");

    failures += check(createtypedUArrayWithArrayLike(),"Create {{.item.objectType}} with ArrayLike");

    failures += check(createTAFromEmptyArrayBuffer(),"Create default empty {{.item.objectType}} from empty source");
    failures += check(createTAFromEmptyArrayBufferOneParamNoOffset(),"Create default empty {{.item.objectType}} from empty source one param");
    failures += check(createTAFromEmptyArrayBufferOneOffsetWithWrongNumber(),"Create default empty {{.item.objectType}} from empty source one param");
    failures += check(createTAFromEmptyArrayBufferTwoParams(),"Create default empty {{.item.objectType}} from empty source two params");
    failures += check(createTAFromEmptyArrayBufferTwoParamsWithWrongNumber(),"Create default empty {{.item.objectType}} from empty source two params with wrong number");

    failures += check(createTAFromNonEmptyArrayBuffer(),"Create default empty {{.item.objectType}} from non empty source");
    failures += check(createTAFromNonEmptyArrayBufferOneParamNoOffset(),"Create default empty {{.item.objectType}} from non empty source one param");
    failures += check(createTAFromNonEmptyArrayBufferOneOffsetWithWrongNumber(),"Create default empty {{.item.objectType}} from non empty source one param with wrong number");
    failures += check(createTAFromNonEmptyArrayBufferTwoParams(),"Create default empty {{.item.objectType}} from non empty source two params");
    failures += check(createTAFromNonEmptyArrayBufferTwoParamsWithWrongNumber(),"Create default empty {{.item.objectType}} from non empty source two params with wrong number");

    failures += check(createTAFromNonEmptyArrayBufferOneParamWithOffset(),"Create default empty {{.item.objectType}} from non empty source with offset");
    failures += check(createTAFromNonEmptyArrayBufferOneParamWithOffsetAndSize(),"Create default empty {{.item.objectType}} from non empty source with offset and size");

    if (failures > 0){
        console.log("failed");
        return fail;
    }

    console.log("All passed");
    return success;
}

function check(result: int, message: String): number {
  if (result == 0) {
    return success;
  }
  console.log("\nFAILED: " + message);
  return fail;
}

function check(result: number, message: String): number {
  if (result == 0) {
    return success;
  }
  console.log("\nFAILED: " + message);
  return fail;
}

const normalSource: {{.item.sourceElementsType}}[] = {{.item.data}};
const abnormalSource: {{.item.sourceElementsType}}[] = {{.item.abnormalData}};

function createDefault(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}();
  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createEmptyWithLength(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}(0);
  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createEmptyWithNaNLength(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}(NaN);
  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createNonEmptyWithFloatingLength(): number {
    let target: {{.item.objectType}} = new {{.item.objectType}}(5.4);
    if (target.length as int == 5 && target.byteOffset as int == 0 && target.byteLength as int == 5 * {{.item.primitiveSizeBytes}}) {
      return success;
    }
    return fail;
}

function createNonEmptyWithLength(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}(5);
  if (target.length as int == 5 && target.byteOffset as int == 0 && target.byteLength as int == 5 * {{.item.primitiveSizeBytes}}){
    return success;
  }
  return fail;
}

function createtypedUArrayWithNegativeLength(): number {
  try {
    let target: {{.item.objectType}} = new {{.item.objectType}}(-1 as number);
  } catch(e) {
    if (e instanceof TypeError) { 
      return success;
    }
    return fail;
  }
  return fail;
}

function createtypedUArrayWithNegativeInfinityLength(): number {
  try {
    let target: {{.item.objectType}} = new {{.item.objectType}}(Number.NEGATIVE_INFINITY);
  } catch(e) {
    if (e instanceof TypeError) {
      return success;
    }
    return fail;
  }
  return fail;
}

function createtypedUArrayWithPositiveInfinityLength(): number {
  try {
    let target: {{.item.objectType}} = new {{.item.objectType}}(Number.POSITIVE_INFINITY);
  } catch(e) {
    if (e instanceof TypeError) {
      return success;
    }
    return fail;
  }
  return fail;
}

function createtypedUArrayWithNegativeNaNLength(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}(-NaN);
  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createtypedUArrayWithPositiveNaNLength(): number {
  let target: {{.item.objectType}} = new {{.item.objectType}}(+NaN);
  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createEmptyWithIterable(): number {
  let array: Array<{{.item.type}}> = new Array<{{.item.type}}>();
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(array.values() as Iterable<{{.item.type}}>);
  } catch(e) {
    return fail;
  }

  for (let value of target) {
    console.log(value);
    return fail;
  }

  if (target.length as int == 0){
    return success;
  }

  return fail;
}

function createNonEmptyWithIterable(): number {
  let array: Array<{{.item.type}}> = new Array<{{.item.type}}>();
  array.push({{.item.create}}(1));
  array.push({{.item.create}}(2));
  array.push({{.item.create}}(3));
  let target: {{.item.objectType}} = new {{.item.objectType}}(array.values() as Iterable<{{.item.type}}>);

  if (target.length as int == 3 && target.byteOffset as int == 0 && target.byteLength as int == 3*{{.item.primitiveSizeBytes}}){
    return success;
  }

  let exceptArray: Array<{{.item.type}}> = new Array<{{.item.type}}>();
  for (let value of target) {
    exceptArray.push(value);
    console.log(value);
  }

  let origin: {{.item.objectType}} = new {{.item.objectType}}(exceptArray.values() as Iterable<{{.item.type}}>);
  for (let i: int = 0; i< target.length as int; i++) {
    let tv = target[i]{{.item.cast2primitive}};
    let ov = origin[i]{{.item.cast2primitive}};
    console.log(tv + "->" + ov);
    if (tv != ov) {
      console.log("Array data mismatch");
      return fail;
    }
  }
  return fail;
}

function testTypedUArrayIterator(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
  let typedUArray: {{.item.objectType}};

  try {
    typedUArray = new {{.item.objectType}}(ss);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let length = typedUArray.length;

  try {
    typedUArray[0] = {{.item.create}}(1);
    typedUArray[1] = {{.item.create}}(2);
    typedUArray[2] = {{.item.create}}(3);
    typedUArray[3] = {{.item.create}}(4);
    typedUArray[4] = {{.item.create}}(5);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let arrayIterator = typedUArray.$_iterator();
  let next = arrayIterator.next();
  let counter: number = 0;
  while (!next.done) {
    if (next.value == undefined) {
        return fail;
    }
    if (typedUArray[counter] != (next.value!)) {
        return fail;
    }
    next = arrayIterator.next()
    counter++
  }

  if (counter != typedUArray.length) {
    return fail;
  }

  return success;
}

function testTypedUArrayIteratorEmptyArray(): number {
  let emptyArray = new {{.item.objectType}}(new ArrayBuffer(0));
  let emptyIterator = emptyArray.$_iterator();
  let emptyNext = emptyIterator.next();

  if (!emptyNext.done || emptyNext.value != undefined) {
    return fail;
  }

  return success;
}

function testTypedUArrayIteratorSingleElement(): number {
  let singleArray = new {{.item.objectType}}(new ArrayBuffer(1 *  {{.item.primitiveSizeBytes}}));

  try {
    singleArray[0] = {{.item.create}}(99);
  } catch(e) {
    console.log(e);
    return fail;
  }

  let singleIterator = singleArray.$_iterator();
  let singleNext = singleIterator.next();

  if (singleNext.done || singleNext.value != {{.item.create}}(99)) {
    return fail;
  }

  singleNext = singleIterator.next();
  if (!singleNext.done || singleNext.value != undefined) {
    return fail;
  }

  return success;
}

function testTypedUArrayIteratorAfterModification(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
  let typedUArray: {{.item.objectType}};

  try {
    typedUArray = new {{.item.objectType}}(ss);
  } catch(e) {
    console.log(e);
    return fail;
  }

  try {
    typedUArray[0] = {{.item.create}}(1);
    typedUArray[1] = {{.item.create}}(2);
    typedUArray[2] = {{.item.create}}(3);
    typedUArray[3] = {{.item.create}}(4);
    typedUArray[4] = {{.item.create}}(5);
  } catch(e) {
    console.log(e);
    return fail;
  }

  // Testing iterator after modification
  let arrayIterator = typedUArray.$_iterator();
  let next = arrayIterator.next();
  let expectedValues: {{.item.type}}[] = [
    {{.item.create}}(1),
    {{.item.create}}(2),
    {{.item.create}}(3),
    {{.item.create}}(4),
    {{.item.create}}(5),
  ];
  let counter: int = 0;

  while (!next.done) {
    if (next.value == undefined) {
      return fail;
    }
    if (expectedValues[counter] != next.value) {
      return fail;
    }
    next = arrayIterator.next();
    counter++;
  }

  if (counter != typedUArray.length) {
    return fail;
  }

  // Modify a value
  typedUArray[0] = {{.item.create}}(99);
  if(typedUArray.$_iterator().next().value != {{.item.create}}(99)){
    return fail;
  }

  return success;
}

function testTypedUArrayIteratorAfterCompletion(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});
  let typedUArray: {{.item.objectType}};

  try {
    typedUArray = new {{.item.objectType}}(ss);
  } catch(e) {
    console.log(e);
    return fail;
  }

  typedUArray[0] = {{.item.create}}(1);
  typedUArray[1] = {{.item.create}}(2);
  typedUArray[2] = {{.item.create}}(3);
  typedUArray[3] = {{.item.create}}(4);
  typedUArray[4] = {{.item.create}}(5);

  let arrayIterator = typedUArray.$_iterator();
  let next = arrayIterator.next();

  while (!next.done) {
    next = arrayIterator.next();
  }

  // Check iterator after completion
  next = arrayIterator.next();
  if (!next.done || next.value != undefined) {
    return fail;
  }

  return success;
}

function createtypedUArrayWithArrayLike(): number {
  let array = new Array<{{.item.type}}>();
  array.push({{.item.create}}(1 as Number));
  array.push({{.item.create}}(2 as Number));
  array.push({{.item.create}}(3 as Number));

  let target: {{.item.objectType}} = new {{.item.objectType}} (array as ArrayLike<{{.item.type}}>);
  if (target.length as int == 3 && target.byteOffset as int == 0){
    return success;
  }
  return fail;
}

function createTAFromEmptyArrayBuffer(): number {
  let ss = new ArrayBuffer(0);  // Buffer 0-bytes length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss);
  } catch(e) {
    return fail;
  }

  if (target.length as int == 0 && target.byteOffset as int == 0) return success;
  return fail;
}

function createTAFromNonEmptyArrayBuffer(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss);
  } catch(e) {
    return fail;
  }

  if (target.byteLength as int == 5 * {{.item.primitiveSizeBytes}} && target.byteOffset as int == 0) return success;
  console.println("Error: Actual bytes length: " + target.byteLength);
  return fail;
}

function createTAFromEmptyArrayBufferOneParamNoOffset(): number {
  let ss = new ArrayBuffer(0);
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 0);
  } catch(e) {
    return fail;
  }
  if (target.length as number != 0 || target.byteOffset as number != 0) {
    return fail;
  }

  try {
    target = new {{.item.objectType}}(ss, undefined);
  } catch(e) {
    return fail;
  }

  if (target.length as number != 0 || target.byteOffset as number != 0) {
    return fail;
  }

  return success;
}

function createTAFromEmptyArrayBufferOneOffsetWithWrongNumber(): number {
  let ss = new ArrayBuffer(0);
  let target: {{.item.objectType}};
  let result: number = success;

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -1);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, 1);
  } catch(e) {
    result = success;
  }

  return result;
}


function createTAFromNonEmptyArrayBufferOneParamNoOffset(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 0);
  } catch(e) {
    return fail;
  }

  if (target.byteLength as number != 5 * {{.item.primitiveSizeBytes}} || target.byteOffset as number != 0
    || target.length as number != 5){
    console.println("Error: Actual bytes length: " + target.byteLength);
    return fail;
  }

  try {
    target = new {{.item.objectType}}(ss, undefined);
  } catch(e) {
    return fail;
  }

  if (target.byteLength as number != 5 * {{.item.primitiveSizeBytes}} || target.byteOffset as number != 0
    || target.length as number != 5){
    console.println("Error: Actual bytes length: " + target.byteLength);
    return fail;
  }

  return success;
}

function createTAFromNonEmptyArrayBufferOneOffsetWithWrongNumber(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
  let target: {{.item.objectType}};
  let result: number = success;
  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -1);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, 6*{{.item.primitiveSizeBytes}});
  } catch(e) {
    result = success;
  }

  return result;
}

function createTAFromNonEmptyArrayBufferOneParamWithOffset(): number {
  let ss = new ArrayBuffer(7*{{.item.primitiveSizeBytes}});  // Buffer 7-items length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 2*{{.item.primitiveSizeBytes}});
  } catch(e) {
    console.println(e);
    return fail;
  }

  if (target.byteLength as int == 5 * {{.item.primitiveSizeBytes}} && target.byteOffset as int == 2*{{.item.primitiveSizeBytes}}
    && target.length as int == 5){
    return success;
  }
  console.println("Error: Actual bytes length: " + target.byteLength);
  console.println("Error: Actual bytes offset: " + target.byteOffset);
  console.println("Error: Actual length: " + target.length);
  return fail;
}

function createTAFromNonEmptyArrayBufferOneParamWithOffsetAndSize(): number {
  let ss = new ArrayBuffer(7*{{.item.primitiveSizeBytes}});  // Buffer 7-items length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 2*{{.item.primitiveSizeBytes}}, 4);
  } catch(e) {
    console.println(e);
    return fail;
  }

  if (target.byteLength as int == 4*{{.item.primitiveSizeBytes}} && target.byteOffset as int == 2*{{.item.primitiveSizeBytes}} && target.length as int == 4) return success;
  console.println("Error: Actual bytes length: " + target.byteLength);
  console.println("Error: Actual bytes offset: " + target.byteOffset);
  console.println("Error: length: " + target.length);
  return fail;
}

function createTAFromEmptyArrayBufferTwoParams(): number {
  let ss = new ArrayBuffer(0);
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 0, 0);
  } catch(e) {
    return fail;
  }

  if (target.length as number != 0 || target.byteOffset as number != 0) {
    return fail;
  }

  try {
    target = new {{.item.objectType}}(ss, undefined, undefined);
  } catch(e) {
    return fail;
  }

  if (target.length as number != 0 || target.byteOffset as number != 0) {
    return fail;
  }

  return success;
}

function createTAFromNonEmptyArrayBufferTwoParams(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
  let target: {{.item.objectType}};
  try {
    target = new {{.item.objectType}}(ss, 0, 5);
  } catch(e) {
    return fail;
  }

  if (target.byteLength as int == 5 * {{.item.primitiveSizeBytes}} && target.byteOffset as int == 0 && target.length == 5){
    return success;
  }
  console.println("Error: Actual bytes length: " + target.byteLength);
  return fail;
}

function createTAFromEmptyArrayBufferTwoParamsWithWrongNumber(): number {
  let ss = new ArrayBuffer(0);  // Buffer 5-items length;
  let target: {{.item.objectType}};
  let result: number = success;
  try {
    result = fail;
    target = new {{.item.objectType}}(ss, new Number(-1), undefined);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, new Number(2), undefined);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -1, -1);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -2, 66);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, 2, 5);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, 2, 66);
  } catch(e) {
    result = success;
  }

  return result;
}

function createTAFromNonEmptyArrayBufferTwoParamsWithWrongNumber(): number {
  let ss = new ArrayBuffer(5 * {{.item.primitiveSizeBytes}});  // Buffer 5-items length;
  let target: {{.item.objectType}};
  let result: number = success;

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, new Number(-1), undefined);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -2, 66);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -1, -1);
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, -1, 6 * {{.item.primitiveSizeBytes}});
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, 2, 6 * {{.item.primitiveSizeBytes}});
  } catch(e) {
    result = success;
  }

  try {
    result = fail;
    target = new {{.item.objectType}}(ss, undefined, new Number(-1));
  } catch(e) {
    result = success;
  }

  return result;
}